﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Practices.Unity.InterceptionExtension;

using Rhino.Mocks;

using SharedGenomics.Workbench.Aspects;

namespace SharedGenomics.Workbench.Tests.Aspects
{
    [TestClass]
    public class CacheCallHandlerTests
    {
        private MockRepository mocks;
        private ICachingStrategy cachingStrategy;
        private IMethodInvocation methodInvocation;
        private GetNextHandlerDelegate nextHandler;
        private InvokeHandlerDelegate invokeHandler;
        private IMethodReturn methodReturn;

        [TestInitialize]
        public void Setup()
        {
            this.mocks = new MockRepository();
            this.cachingStrategy = this.mocks.Stub<ICachingStrategy>();
            this.methodInvocation = this.mocks.Stub<IMethodInvocation>();
            this.methodReturn = this.mocks.Stub<IMethodReturn>();

            this.nextHandler = () => this.invokeHandler;
            this.invokeHandler = (i, nh) => this.methodReturn;
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ShouldThrowIfNoArguments()
        {
            using (this.mocks.Record())
            {
                IParameterCollection @params = this.mocks.Stub<IParameterCollection>();
                this.methodInvocation.Stub(mi => mi.Arguments).Return(@params);
                @params.Stub(p => p.Count).Return(0);
            }

            using (this.mocks.Playback())
            {
                var handler = this.CreateHandler();
                handler.Invoke(this.methodInvocation, this.nextHandler);
            }
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ShouldThrowIfArgumentNotInt()
        {
            using (this.mocks.Record())
            {
                IParameterCollection @params = this.mocks.Stub<IParameterCollection>();
                this.methodInvocation.Stub(mi => mi.Arguments).Return(@params);
                @params.Stub(p => p.Count).Return(1);
                @params[0] = "abc";
            }

            using (this.mocks.Playback())
            {
                var handler = this.CreateHandler();
                handler.Invoke(this.methodInvocation, this.nextHandler);
            }
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ShouldThrowIfMoreThanOneArgument()
        {
            using (this.mocks.Record())
            {
                IParameterCollection @params = this.mocks.Stub<IParameterCollection>();
                this.methodInvocation.Stub(mi => mi.Arguments).Return(@params);
                @params.Stub(p => p.Count).Return(2);
            }

            using (this.mocks.Playback())
            {
                var handler = this.CreateHandler();
                handler.Invoke(this.methodInvocation, this.nextHandler);
            }
        }

        [TestMethod]
        public void ShouldReturnCachedValue()
        {
            this.methodInvocation = this.mocks.DynamicMock<IMethodInvocation>();

            using (this.mocks.Record())
            {
                IParameterCollection @params = this.mocks.Stub<IParameterCollection>();
                this.methodInvocation.Stub(mi => mi.Arguments).Return(@params);
                @params.Stub(p => p.Count).Return(1);
                @params[0] = 1;

                this.methodInvocation.Expect(mi => mi.CreateMethodReturn("abc")).Return(this.mocks.Stub<IMethodReturn>());

                object item;
                this.cachingStrategy.Stub(cs => cs.TryGetFromCache(1, out item)).Return(true).OutRef("abc");
            }

            using (this.mocks.Playback())
            {
                var handler = this.CreateHandler();
                handler.Invoke(this.methodInvocation, this.nextHandler);
            }
        }

        [TestMethod]
        public void ShouldInvokeTarget()
        {
            using (this.mocks.Record())
            {
                IParameterCollection @params = this.mocks.Stub<IParameterCollection>();
                this.methodInvocation.Stub(mi => mi.Arguments).Return(@params);
                @params.Stub(p => p.Count).Return(1);
                @params[0] = 1;

                object item;
                this.cachingStrategy.Stub(cs => cs.TryGetFromCache(1, out item)).Return(false);
                this.cachingStrategy.Stub(cs => cs.AddToCache(1, null));
            }

            using (this.mocks.Playback())
            {
                var handler = this.CreateHandler();
                var @retVal =  handler.Invoke(this.methodInvocation, this.nextHandler);

                Assert.AreSame(this.methodReturn, @retVal);
            }
        }

        [TestMethod]
        public void ShouldAddToCache()
        {
            using (this.mocks.Record())
            {
                IParameterCollection @params = this.mocks.Stub<IParameterCollection>();
                this.methodInvocation.Stub(mi => mi.Arguments).Return(@params);
                @params.Stub(p => p.Count).Return(1);
                @params[0] = 1;

                object item;
                this.cachingStrategy.Stub(cs => cs.TryGetFromCache(1, out item)).Return(false);
                this.cachingStrategy.Expect(cs => cs.AddToCache(1, "abc"));

                this.methodReturn.ReturnValue = "abc";
            }

            using (this.mocks.Playback())
            {
                var handler = this.CreateHandler();
                handler.Invoke(this.methodInvocation, this.nextHandler);
            }
        }

        [TestMethod]
        public void ShouldNotAddToCacheIfTargetThrows()
        {
            this.cachingStrategy = this.mocks.StrictMock<ICachingStrategy>();

            using (this.mocks.Record())
            {
                IParameterCollection @params = this.mocks.Stub<IParameterCollection>();
                this.methodInvocation.Stub(mi => mi.Arguments).Return(@params);
                @params.Stub(p => p.Count).Return(1);
                @params[0] = 1;

                object item;
                this.cachingStrategy.Stub(cs => cs.TryGetFromCache(1, out item)).Return(false);

                this.methodReturn.Exception = new ArgumentException();
            }

            using (this.mocks.Playback())
            {
                var handler = this.CreateHandler();
                handler.Invoke(this.methodInvocation, this.nextHandler);
            }
        }

        private CacheCallHandler CreateHandler()
        {
            return new CacheCallHandler(this.cachingStrategy);
        }
    }
}
